--[[------------------------------------------------------------------
--  $id$
--
--  This is a build script tailored specifically for the Dongle
--  framework.  It will take the build recipe from Recipe.lua and
--  run all unit tests, and package the files for tagging or release.
--
--  Copyright 2007 by James N. Whitehead II
------------------------------------------------------------------]]--

require("lunit/lunit")
lunit.import("all")

-- This is the recipe for the build script

local modules = {
	{ -- DongleStub
		name = "DongleStub",
		includeHeader = true,
		tests = {
			"api.lua",
			"register.lua",
		},
	},
	{ -- DongleCore
		name = "DongleCore",
		includeHeader = true,
		tests = {
			"api.lua",
			"register.lua"
		},
	},
}

-- Determine the path seperator for this operating system
local sep = package.path:match("(%p)%?%.")

-- Determine the root path of the build script, so we can ensure
-- we work relative to that directory
local root
if sep == "\\" then
	root = arg[0]:match("(.*)"..sep..sep.."?.+") or "."
else
	root = arg[0]:match("(.*)"..sep..".+") or "."
end

-- Make an easy printf function
local function argsToStrings(a1, ...)
	if select("#", ...) > 0 then
		return tostring(a1), argsToStrings(...)
	else
		return tostring(a1)
	end
end

local function printf(fmt, ...)
	print(fmt:format(argsToStrings(...)))
end

-- Utility function to create a new copy of a table.  New tables
-- are created as necessary, so the new table will not reference
-- anything other than non-table values in the old table
local done = {}
function copyTable(tbl, recurse)
	if not recurse then
		done = {}
	end

	local new = {}

	if done[tbl] then return done[tbl] end

	for k,v in pairs(tbl) do
		done[tbl] = tbl
		if type(k) == "table" then
			if not done[k] then
				k = copyTable(k, true)
				done[k] = k
			else
				k = done[k]
			end
		end
		
		if type(v) == "table" then
			if not done[v] then
				v = copyTable(v, true)
				done[v] = v
			else
				v = done[v]
			end
		end
		new[k] = v
	end

	return new
end

-- Simple helper function to search the subdirectories of the build
-- path for a given filename.  This allows us to do things like
-- require("DongleStub") in a unit test, without needing to worry
-- about where in the file system we are.
local function searchFilePath(file)
	for idx,entry in ipairs(modules) do
		local dirname = entry.name
		local filename = ("%s%s%s%s%s"):format(root, sep, dirname, sep, file)
		local testfile = io.open(filename)
		if testfile then return filename end
	end

	local filename = ("%s%s%s"):format(root, sep, file)
	local testfile = io.open(filename)
	return testfile and filename
end

local function getCleanEnv()
	local genv = getfenv(0)
	local cleanenv = copyTable(genv)
	local loadfile = genv.loadfile

	cleanenv.debug = nil
	cleanenv.io = nil

	function cleanenv.getfenv(...)
		return cleanenv
	end

	-- Redefine the file functions to look in the right paths
	function cleanenv.require(file)
		local filename = searchFilePath(file)
		if not filename then return nil end
		local succ,err = loadfile(filename)
		assert(succ, err)
		if type(succ) == "function" then
			setfenv(succ, cleanenv)
		end
		succ()
	end

	cleanenv.dofile = cleanenv.require

	function cleanenv.loadfile(file)
		local filename = searchFilePath(file)
		if not filename then return nil end
		local succ,err = loadfile(filename)
		if type(succ) == "function" then
			setfenv(succ, cleanenv)
		end
		return succ,err
	end

	function cleanenv.argerror(num,name,expected,got)
		local badarg = "bad argument #%s to '%s' (%s expected, got %s)"
		return badarg:format(num, name, expected, got)
	end

	function cleanenv.makelib(maj,min)
		local lib = {}
		function lib:GetVersion() return maj,min end
		return lib
	end

	cleanenv.types = {
		["string"] = "test string",
		["number"] = 1,
		["table"] = {},
		["func"] = function() end,
		["thread"] = coroutine.create(function() end),
	}

	return setmetatable({}, {__index=cleanenv})
end

-- Run through all unit tests before building
for idx,entry in ipairs(modules) do
	local modname = entry.name
	local tests = entry.tests

	if next(tests) then
	--	print(" * Running unit tests for " .. modname)
	end

	for idx,testfile in pairs(tests) do 
		local filename = ("%s%s%s%s%s%s%s"):format(root, sep, modname, sep, "tests", sep, testfile)
		local clean = getCleanEnv()
		local succ, err = loadfile(filename)
		if not succ then
			print("Could not load test file: " .. filename)
		else
			setfenv(succ, clean)
			succ()

			-- Make a sorted list of all unit tests just created
			local tbl = {}
			for k,v in pairs(clean) do table.insert(tbl, k) end
			table.sort(tbl)

			local testcase = lunit.TestCase(filename)
			
			for idx,k in pairs(tbl) do 
				local v = clean[k]
				local env = getCleanEnv()
				setfenv(v, env)
				testcase[k] = v
			end
		end
	end
end

lunit.run()